/*
 * yapfs.c
 * romfs 为只读文件系统,ext4太复杂,fat32又不是正统的linux文件系统
 * 为后续研究mmap和加载elf做准备,实现一个简单的文件系统,不至于把大量时间花在研究文件系统上
 * 只支持一个文件,仅支持读写(后续会加入mmap),不支持目录,不支持创建、删除
 */

#include "linux/fs.h"
#include "linux/printk.h"
#include "linux/string.h"
#include "linux/slab.h"
#include "linux/error.h"
#include "linux/blk.h"
#include "linux/common.h"
#include "linux/yapfs.h"

int yapfs_open(struct inode *inode, struct file *fp)
{
    return 0;
}

void yapfs_close(struct inode *inode, struct file *fp)
{

}

int yapfs_read(struct inode *inode, struct file *filp, char *buf, int len)
{
    int ret;
    char *buff;
    int block_size;
    int start_block;
    int end_block;
    int nr_blocks = 0;
    struct yapfs_inode *y_inode;
    struct yapfs_super_block *yapfs_sb;

    if (!len)
        return len;
    
    if (!inode || !inode->i_sb)
        return -EINVAL;
    
    y_inode = inode->i_what;
    yapfs_sb = inode->i_sb->sb_data;
    block_size = yapfs_sb->block_size;
    
    if ((filp->f_pos + len) > y_inode->size)
        len = y_inode->size - filp->f_pos;
    
    start_block = filp->f_pos / block_size + y_inode->start_block;
    end_block = (filp->f_pos + len + block_size - 1) / block_size + y_inode->start_block;
    nr_blocks = end_block - start_block;

    buff = kmalloc(nr_blocks * block_size, GFP_KERNEL);
    if (!buff)
        return -ENOMEM;

#if 0
    printk("\nfilp->f_pos: %d\n", filp->f_pos);
    printk("len: %d\n", len);
    printk("nr_blocks: %d\n", nr_blocks);
    printk("rw_block: %d\n", rw_block);
    printk("filp offset: %d\n", filp->f_pos % block_size);
#endif

    ret = block_read(inode, start_block, buff, nr_blocks);
    if (ret < 0)
        goto out;

    memcpy(buf, buff + filp->f_pos % block_size, len);
    
    filp->f_pos += len;

out:
    kfree(buff);
    return len;
}

int yapfs_write(struct inode *inode, struct file *filp, char *buf, int len)
{
    int ret;
    char *buff;
    int block_size;
    int start_block;
    int end_block;
    int nr_blocks = 0;
    struct yapfs_inode *y_inode;
    struct yapfs_super_block *yapfs_sb;

    if (!len)
        return len;
    
    if (!inode || !inode->i_sb)
        return -EINVAL;
    
    y_inode = inode->i_what;
    yapfs_sb = inode->i_sb->sb_data;
    block_size = yapfs_sb->block_size;
    
    if ((filp->f_pos + len) > y_inode->size)
        len = y_inode->size - filp->f_pos;
    
    start_block = filp->f_pos / block_size + y_inode->start_block;
    end_block = (filp->f_pos + len + block_size - 1) / block_size + y_inode->start_block;
    nr_blocks = end_block - start_block;

    buff = kmalloc(nr_blocks * block_size, GFP_KERNEL);
    if (!buff)
        return -ENOMEM;
    
    printk("\nfilp->f_pos: %d\n", filp->f_pos);
    printk("len: %d\n", len);
    printk("nr_blocks: %d\n", nr_blocks);
    printk("rw_block: %d\n", start_block);
    printk("filp offset: %d\n", filp->f_pos % block_size);
    
    if (filp->f_pos % block_size)
    {
        ret = block_read(inode, start_block, buff, 1);
        if (ret < 0)
            goto out;
    }

    memcpy(buff + filp->f_pos % block_size, buf, len);
    ret = block_write(inode, start_block, buff, nr_blocks);
    if (ret < 0)
        goto out;
    
    filp->f_pos += len;

out:
    kfree(buff);
    return len;
}

int yapfs_lookup(struct inode *dir, char *name, int namelen, struct inode **res_inode)
{
    struct yapfs_inode *y_inode;
   
    y_inode = dir->i_what;
    if (!y_inode)
        return -EINVAL;
    
    if (strcmp(y_inode->name, name) == 0)
    {
        *res_inode = dir;
        return 0;
    }
    else 
        return -EINVAL;
}

int yapfs_create(struct inode *dir, char *name, int namelen, int mode, struct inode **res_inode)
{
    return 0;
}

extern struct inode_operations chrdev_inode_operations;
extern struct inode_operations blkdev_inode_operations;

int yapfs_mknod(struct inode *dir, char *name, int namelen, int mode, int dev_num)
{
    return 0;
}

struct file_operations yapfs_file_operations = 
{
    yapfs_open,
    yapfs_close,
    yapfs_read,
    yapfs_write,
    NULL,
    NULL,
    NULL,
};

struct inode_operations yapfs_inode_operations = 
{
    &yapfs_file_operations, 
    yapfs_create,
    yapfs_lookup,
    NULL,
    NULL,
    yapfs_mknod,
    NULL,
};


struct super_block *yapfs_read_super(struct super_block *sb)
{
    int ret;
    char *buff;
    struct inode *inode;
    struct yapfs_inode *yapfs_inode;
    struct yapfs_super_block *yapfs_sb;

    inode = kmalloc(sizeof (struct inode), GFP_KERNEL);
    if (!inode)
        return NULL;
    
    yapfs_inode = kmalloc(sizeof (struct yapfs_inode), GFP_KERNEL);
    if (!yapfs_inode)
        goto err_alloc_yapfs_inode;
    
    yapfs_sb = kmalloc(sizeof (struct yapfs_super_block), GFP_KERNEL);
    if (!yapfs_sb)
        goto err_alloc_yapfs_sb;
   
    inode->i_dev = sb->s_dev;

    buff = kmalloc(YAPFS_BLOCK_SIZE, GFP_KERNEL);
    if (!buff)
        goto err_alloc_rwbuff;

    ret = block_read(inode, 0, buff, 1);
    if (ret < 0)
        goto err_io;
    
    memcpy(yapfs_sb, buff, sizeof (struct yapfs_super_block));
    memcpy(yapfs_inode, buff + sizeof (struct yapfs_super_block), sizeof (struct yapfs_inode));

    inode->i_op = &yapfs_inode_operations;
    inode->i_what = yapfs_inode;
    inode->i_sb = sb;
    
    sb->s_type = FILE_SYSTEM_TYPE_YAPFS;
    sb->s_mounted = inode;
    sb->sb_data = yapfs_sb;
    
    kfree(buff);
    return sb;

err_io:
    kfree(buff);
err_alloc_rwbuff:
    kfree(yapfs_sb);
err_alloc_yapfs_sb:
    kfree(yapfs_inode);
err_alloc_yapfs_inode:
    kfree(inode);
    return NULL;
}
